#!/bin/bash

# OpenSSL测试证书生成脚本
# 用途：系统性生成各种规格、算法、曲线和场景的测试证书

set -e

# 配置参数
BASE_DIR="certs"
DAYS=365
PASSWORD="password"
COUNTRY="CN"
STATE="Shanghai"
LOCALITY="Shanghai"
ORGANIZATION="Test_Organization"
ORG_UNIT="Test_Unit"

# 清理并创建基础目录
rm -rf "$BASE_DIR"
mkdir -p "$BASE_DIR"

# 日志函数
log() {
    echo -e "\033[1;32m[$(date '+%Y-%m-%d %H:%M:%S')] $1\033[0m"
}

# 错误处理函数
error() {
    echo -e "\033[1;31m[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1\033[0m"
    exit 1
}

# 检查OpenSSL版本
check_openssl_version() {
    OPENSSL_VERSION=$(openssl version | awk '{print $2}')
    # 检查版本是否支持所有CRL原因代码
    if [[ "$OPENSSL_VERSION" == 1.0.* || "$OPENSSL_VERSION" == 1.1.* ]]; then
        log "检测到OpenSSL $OPENSSL_VERSION，使用兼容的CRL原因代码"
        USE_COMPAT_REASONS=true
    else
        USE_COMPAT_REASONS=false
    fi
}

# 获取兼容的CRL原因代码
get_compatible_reason() {
    local reason=$1
    # 对于旧版本OpenSSL，将不支持的原因代码映射到supported原因
    if [ "$USE_COMPAT_REASONS" = true ]; then
        case "$reason" in
            "privilegeWithdrawn") echo "cessationOfOperation" ;;
            "AACompromise") echo "keyCompromise" ;;
            *) echo "$reason" ;;
        esac
    else
        echo "$reason"
    fi
}

# 解析函数参数的公共函数
parse_fun_args() {
    local in out san
    local algo="RSA"
    local bits="2048"
    local curve="P-256"
    local md="SHA256"
    OPTIND=1
    while getopts "i:o:a:b:c:m:s:" opt; do
        case "$opt" in
            i) in=${OPTARG} ;;
            o) out=${OPTARG} ;;
            a) algo=${OPTARG} ;;
            b) bits=${OPTARG} ;;
            c) curve=${OPTARG} ;;
            m) md=${OPTARG} ;;
            s) san=${OPTARG} ;;
            \?)
                echo "无效选项： -$OPTARG" >&2
                exit 1
                ;;
            :)
                echo "选项需要参数" >&2
                exit 1
                ;;
        esac
    done
    shift $((OPTIND-1))

    if [ -z $out ]; then
        error "-o are necessary."
        exit 1
    fi
    
    echo "$in|$out|$algo|$bits|$curve|$md|$san|$*|"
    return 0
}

# 创建配置文件
create_config() {
    local path san
    local is_ca="false"
    local md="SHA256"
    OPTIND=1
    while getopts "o:i:s:m:" cfg_opt; do
        case "$cfg_opt" in
            o) path=${OPTARG} ;;
            i) is_ca=${OPTARG} ;;
            s) san=${OPTARG} ;;
            m) md=${OPTARG} ;;
            \?)
                echo "无效选项： -$OPTARG" >&2
                exit 1
                ;;
            :)
                echo "选项需要参数" >&2
                exit 1
                ;;
        esac
    done
    shift $((OPTIND-1))

    local dir=${path%/*}
    local name=${path##*/}

    touch "${path}.index"
    echo "1000" > "${path}.serial"
    echo "1000" > "${path}.crlnumber"

    cat > "${path}.cnf" <<EOF
[req]
default_bits = 2048
prompt = no
default_md = ${md}
distinguished_name = dn
attributes = req_attributes
$(if [ "$is_ca" = "true" ]; then
echo "x509_extensions = v3_ca"
else
echo "req_extensions = v3_req"
fi)

[dn]
C = $COUNTRY
ST = $STATE
L = $LOCALITY
O = $ORGANIZATION
OU = $ORG_UNIT
CN = $path

[req_attributes]
challengePassword = A_challenge_password
unstructuredName = An_optional_company_name

$(if [ "$is_ca" = "true" ]; then
cat <<EOF_CA
[ca]
default_ca = CA_default

[CA_default]
database = ${path}.index
new_certs_dir = $dir
certificate = ${path}.crt
private_key = ${path}.key
serial = ${path}.serial
crlnumber = ${path}.crlnumber
crl = ${path}.crl
default_days = $DAYS
default_md = ${md}
default_crl_days = 30  # 设置CRL更新周期
preserve = no
policy = policy_anything
email_in_dn = no
rand_serial = yes

[policy_anything]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_crl ]
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
crlDistributionPoints = @crl_distribution_points

[crl_distribution_points]
fullname = URI:http://crl.example.com/${name}.crl

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
$(if [ -n "$san" ]; then
echo "subjectAltName = $san"
fi)
extendedKeyUsage = serverAuth, clientAuth
EOF_CA
else
cat <<EOF_ENDENTITY
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
$(if [ -n "$san" ]; then
echo "subjectAltName = $san"
fi)
extendedKeyUsage = serverAuth, clientAuth
EOF_ENDENTITY
fi)
EOF
}

# 创建私钥
create_pkey() {
    local path=$1
    local algo=$2
    local bits=$3
    local curve=$4

    # 根据算法类型生成私钥
    if [ "$algo" = "RSA" ]; then
        openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:${bits} -out "${path}.key"
    elif [ "$algo" = "EC" ]; then
        openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:${curve} -out "${path}.key"
    elif [ "$algo" = "RSA-PSS" ]; then
        openssl genpkey -algorithm RSA-PSS -pkeyopt rsa_keygen_bits:${bits} -out "${path}.key"
    elif [ "$algo" = "ED25519" ]; then
        openssl genpkey -algorithm ED25519 -out "${path}.key"
    elif [ "$algo" = "ED448" ]; then
        openssl genpkey -algorithm ED448 -out "${path}.key"
    elif [ "$algo" = "X25519" ]; then
        openssl genpkey -algorithm X25519 -out "${path}.key"
    elif [ "$algo" = "X448" ]; then
        openssl genpkey -algorithm X448 -out "${path}.key"
    fi
}

# 创建根CA
create_root_ca() {
    local result="$(parse_fun_args $@)" || return 1
    local path algo bits curve md
    IFS='|' read -r _ path algo bits curve md _ _ <<< $result
    
    log "创建根CA: $path ($algo/$bits/$curve/$md)"
    
    # 创建私钥
    create_pkey $path $algo $bits $curve
    
    # 创建配置文件
    create_config -m "$md" -o "$path" -i "true"
    
    # 生成自签名根CA证书
    openssl req -new -x509 -days $DAYS -key "${path}.key" -out "${path}.crt" -config "${path}.cnf"
}

# 创建中间CA（由根CA签名）
create_intermediate_ca() {
    local result="$(parse_fun_args $@)" || return 1
    local ca_path path algo bits curve md
    IFS='|' read -r ca_path path algo bits curve md _ _ <<< $result
    
    log "创建中间CA: $path ($algo/$bits/$curve/$md)，由 $ca_path 签名"
    
    # 创建私钥
    create_pkey $path $algo $bits $curve
    
    # 创建配置文件
    create_config -m "$md" -o "$path" -i "true"
    
    # 生成中间CA的CSR
    openssl req -new -key "${path}.key" -out "${path}.csr" -config "${path}.cnf"
    
    # 使用根CA签署中间CA证书
    openssl ca -batch -config "${ca_path}.cnf" -days $DAYS -in "${path}.csr" -out "${path}.crt" -extensions v3_ca
}

# 创建设备证书
create_ee_cert() {
    local result="$(parse_fun_args $@)" || return 1
    local ca_path path algo bits curve md san
    IFS='|' read -r ca_path path algo bits curve md san _ <<< $result
    
    log "创建设备证书: $path ($algo/$bits/$curve/$md)"
    
    # 创建私钥
    create_pkey $path $algo $bits $curve
    
    # 创建配置文件
    create_config -s "$san" -m "$md" -o "$path" -i "false"
    
    # 生成CSR
    openssl req -new -key "${path}.key" -out "${path}.csr" -config "${path}.cnf"
    
    # 使用CA签署证书
    openssl ca -batch -config "${ca_path}.cnf" -days $DAYS -in "${path}.csr" -out "${path}.crt" -extensions v3_req
}

# 创建CRL
create_crl() {
    local path=$1
    log "创建CRL: $path"
    openssl ca -gencrl -config "${path}.cnf" -out "${path}.crl"
}

# 吊销证书
revoke_cert() {
    local ca_path=$1
    local path=$2
    local reason=$3
    
    # 获取兼容的原因代码
    compatible_reason=$(get_compatible_reason "$reason")
    
    log "吊销证书: $path (原因: $reason -> $compatible_reason)"
    
    openssl ca -config "${ca_path}.cnf" -revoke "${path}.crt" -crl_reason $compatible_reason
}

# 生成不同格式的证书
convert_certs() {
    local path=$1
    
    log "转换证书格式: $path"
    
    # 转换为DER格式
    openssl x509 -outform der -in "${path}.crt" -out "${path}.der"
    
    # 创建PKCS#12文件
    openssl pkcs12 -export -out "${path}.pfx" -inkey "${path}.key" -in "${path}.crt" -password pass:$PASSWORD

    # 创建PKCS#7文件
    openssl crl2pkcs7 -nocrl -certfile "${path}.crt" -out "${path}.p7b"
}

# 创建过期证书
create_expired_cert() {
    local result="$(parse_fun_args $@)" || return 1
    local ca_path path algo bits curve
    IFS='|' read -r ca_path path algo bits curve _ _ _ <<< $result
    
    log "创建过期证书: $path ($algo/$bits/$curve)"

    # 创建私钥
    create_pkey $path $algo $bits $curve
    
    # 创建配置文件
    create_config -o "$path" -i "false"
    
    # 生成CSR
    openssl req -new -key "${path}.key" -out "${path}.csr" -config "${path}.cnf"
    
    # 使用CA签署证书（有效期设置为过去）
    openssl ca -batch -config "${ca_path}.cnf" -startdate 20000101000000Z -enddate 20010101000000Z -in "${path}.csr" -out "${path}.crt" -extensions v3_req
}

# 创建未生效证书
create_future_cert() {
    local result="$(parse_fun_args $@)" || return 1
    local ca_path path algo bits curve
    IFS='|' read -r ca_path path algo bits curve _ _ _ <<< $result
    
    log "创建未生效证书: $path ($algo/$bits/$curve)"

    # 创建私钥
    create_pkey $path $algo $bits $curve
    
    # 创建配置文件
    create_config -o "$path" -i "false"
    
    # 生成CSR
    openssl req -new -key "${path}.key" -out "${path}.csr" -config "${path}.cnf"
    
    # 使用CA签署证书（有效期设置为未来）
    openssl ca -batch -config "${ca_path}.cnf" -startdate 20300101000000Z -enddate 20310101000000Z -in "${path}.csr" -out "${path}.crt" -extensions v3_req
}

generate_algo_test_inner() {
    local result="$(parse_fun_args $@)" || return 1
    local outdir algo bits curve
    IFS='|' read -r _ outdir algo bits curve _ _ _ <<< $result

    # 创建根CA
    local root_dir="${BASE_DIR}/algorithms/${outdir}"
    mkdir -p "$root_dir"
    create_root_ca -o "${root_dir}/root_ca" -a ${algo} -b ${bits} -c ${curve}
    
    # 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${root_dir}/int_ca" -i "${root_dir}/root_ca" -a ${algo} -b ${bits} -c ${curve}
    
    # 创建服务器和客户端证书（由中间CA签名）
    create_ee_cert -o "${root_dir}/server" -i "${root_dir}/int_ca" -a ${algo} -b ${bits} -c ${curve} -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${root_dir}/client" -i "${root_dir}/int_ca" -a ${algo} -b ${bits} -c ${curve}
    create_crl "${root_dir}/int_ca"
    convert_certs "${root_dir}/server"
    convert_certs "${root_dir}/client"
}

# 生成不同算法和曲线的证书
generate_algorithm_tests() {
    # RSA
    generate_algo_test_inner -o "rsa_2048" -a RSA -b 2048
    generate_algo_test_inner -o "rsa_4096" -a RSA -b 4096

    # RSA-PSS
    generate_algo_test_inner -o "rsa_pss_2048" -a RSA-PSS -b 2048
    generate_algo_test_inner -o "rsa_pss_3072" -a RSA-PSS -b 3072

    # EC
    generate_algo_test_inner -o "ec_p256" -a "EC" -c "P-256"
    generate_algo_test_inner -o "ec_p384" -a "EC" -c "P-384"

    # ED25519 / ED448
    generate_algo_test_inner -o "ed25519" -a "ED25519"
    generate_algo_test_inner -o "ed448" -a "ED448"
}

# 生成证书链测试
generate_chain_tests() {
    local root_dir="$BASE_DIR/chains"
    
    # 公共CA文件
    chain_common_dir="$root_dir/common"
    mkdir -p "$chain_common_dir"

    ## 创建根CA
    create_root_ca -o "${chain_common_dir}/root_ca" -a "RSA"
    ## 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${chain_common_dir}/int_ca" -i "${chain_common_dir}/root_ca" -a "RSA"
    ## 创建设备证书（由中间证书签名）
    create_ee_cert -o "${chain_common_dir}/server" -i "${chain_common_dir}/int_ca" -a "RSA" -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${chain_common_dir}/client" -i "${chain_common_dir}/int_ca" -a "RSA"
    ## 创建另一个根CA和中间CA
    create_root_ca -o "${chain_common_dir}/root_ca_2" -a "RSA"
    create_intermediate_ca -o "${chain_common_dir}/int_ca_2" -i "${chain_common_dir}/root_ca_2" -a "RSA"
    ## 使用不同CA签署的证书
    create_ee_cert -o "${chain_common_dir}/server_2" -i "${chain_common_dir}/int_ca_2" -a "RSA" -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${chain_common_dir}/client_2" -i "${chain_common_dir}/int_ca_2" -a "RSA"
    ## 创建吊销列表（吊销中间证书）
    create_crl "${chain_common_dir}/int_ca"

    # 正常证书链文件
    cat "$chain_common_dir/server.crt" "$chain_common_dir/int_ca.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/server_fullchain.crt"
    cat "$chain_common_dir/client.crt" "$chain_common_dir/int_ca.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/client_fullchain.crt"
    cat "$chain_common_dir/server.crt" "$chain_common_dir/int_ca.crt" > "$root_dir/server_chain_without_rootca.crt"
    cat "$chain_common_dir/client.crt" "$chain_common_dir/int_ca.crt" > "$root_dir/client_chain_without_rootca.crt"
    
    # 缺少中间证书的链
    cat "$chain_common_dir/server.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/server_incomplete_chain.crt"
    cat "$chain_common_dir/client.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/client_incomplete_chain.crt"
    
    # 不匹配的证书链
    cat "$chain_common_dir/server_2.crt" "$chain_common_dir/int_ca.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/server_mismatch_chain.crt"
    cat "$chain_common_dir/client_2.crt" "$chain_common_dir/int_ca.crt" "$chain_common_dir/root_ca.crt" > "$root_dir/client_mismatch_chain.crt"
}

# 生成吊销证书测试
generate_revocation_tests() {
    local root_dir="$BASE_DIR/revocation"
    
    # 创建根CA
    revocation_root_dir="$root_dir/common"
    mkdir -p "$revocation_root_dir"
    create_root_ca -o "${revocation_root_dir}/root_ca" -a "RSA"
    
    # 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${revocation_root_dir}/int_ca" -i "${revocation_root_dir}/root_ca" -a "RSA"
    
    # 正常证书和吊销证书
    revocation_dir="$root_dir/basic"
    mkdir -p "$revocation_dir"
    
    # 正常证书
    create_ee_cert -o "${revocation_dir}/server" -i "${revocation_root_dir}/int_ca" -a "RSA" -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${revocation_dir}/client" -i "${revocation_root_dir}/int_ca" -a "RSA"
    
    # 吊销证书
    create_ee_cert -o "${revocation_dir}/server_revoked" -i "${revocation_root_dir}/int_ca" -a "RSA" -s "DNS:revoked.example.com"
    create_ee_cert -o "${revocation_dir}/client_revoked" -i "${revocation_root_dir}/int_ca" -a "RSA"
    
    # 吊销证书
    revoke_cert "${revocation_root_dir}/int_ca" "${revocation_dir}/server_revoked" "keyCompromise"
    revoke_cert "${revocation_root_dir}/int_ca" "${revocation_dir}/client_revoked" "keyCompromise"
    
    # 生成CRL
    create_crl "${revocation_root_dir}/int_ca"
    
    # 不同原因的吊销
    reasons_dir="$root_dir/reasons"
    mkdir -p "$reasons_dir"
    
    # 为不同原因创建证书并吊销
    reasons=("keyCompromise" "CACompromise" "affiliationChanged" "superseded" "cessationOfOperation" "certificateHold" "removeFromCRL" "privilegeWithdrawn" "AACompromise")
    
    for reason in "${reasons[@]}"; do
        create_ee_cert -o "${reasons_dir}/server_${reason}" -i "${revocation_root_dir}/int_ca" -a "RSA" -s "DNS:${reason}.example.com"
        revoke_cert "${revocation_root_dir}/int_ca" "${reasons_dir}/server_${reason}" "$reason"
    done
    
    # 生成CRL
    create_crl "${revocation_root_dir}/int_ca"
}

# 生成特殊证书测试
generate_special_certs() {
    local root_dir="$BASE_DIR/special_certs"
    
    # 创建根CA
    special_root_dir="$root_dir/common"
    mkdir -p "$special_root_dir"
    create_root_ca -o "${special_root_dir}/root_ca" -a "RSA"
    
    # 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${special_root_dir}/int_ca" -i "${special_root_dir}/root_ca" -a "RSA"
    
    # 自签名证书
    self_signed_dir="$root_dir/self_signed"
    mkdir -p "$self_signed_dir"
    
    # 生成自签名服务器证书
    openssl req -x509 -newkey rsa:2048 -keyout "${self_signed_dir}/server.key" -out "${self_signed_dir}/server.crt" -days $DAYS -nodes -subj "/CN=Self_Signed_Server"
    openssl req -x509 -newkey rsa:2048 -keyout "${self_signed_dir}/client.key" -out "${self_signed_dir}/client.crt" -days $DAYS -nodes -subj "/CN=Self_Signed_Client"
    
    # 过期证书
    expired_dir="$root_dir/expired"
    mkdir -p "$expired_dir"
    
    # 创建过期的服务器和客户端证书
    create_expired_cert -o "${expired_dir}/server" -i "${special_root_dir}/int_ca" -a "RSA"
    create_expired_cert -o "${expired_dir}/client" -i "${special_root_dir}/int_ca" -a "RSA"
    create_crl "${special_root_dir}/int_ca"
    
    # 未生效证书
    future_dir="$root_dir/future"
    mkdir -p "$future_dir"
    
    # 创建未生效的服务器和客户端证书
    create_future_cert -o "${future_dir}/server" -i "${special_root_dir}/int_ca" -a "RSA"
    create_future_cert -o "${future_dir}/client" -i "${special_root_dir}/int_ca" -a "RSA"
    create_crl "${special_root_dir}/int_ca"
    
    # 带SAN的证书
    san_dir="$root_dir/san"
    mkdir -p "$san_dir"
    
    # 创建带SAN的服务器证书
    create_ee_cert -o "${san_dir}/server" -i "${special_root_dir}/int_ca" -a "RSA" -s "DNS:localhost,DNS:example.com,IP:127.0.0.1,IP:192.168.1.1"
    create_ee_cert -o "${san_dir}/client" -i "${special_root_dir}/int_ca" -a "RSA"
    create_crl "${special_root_dir}/int_ca"
}

# 生成不同哈希算法的证书
generate_hash_algorithm_tests() {
    local root_dir="$BASE_DIR/hash_algorithms"
    
    # SHA-256证书
    sha256_dir="$root_dir/sha256"
    mkdir -p "$sha256_dir"
    
    # 创建根CA
    create_root_ca -o "${sha256_dir}/root_ca" -a "RSA"
    
    # 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${sha256_dir}/int_ca" -i "${sha256_dir}/root_ca" -a "RSA"
    
    # 创建服务器和客户端证书
    create_ee_cert -o "${sha256_dir}/server" -i "${sha256_dir}/int_ca" -a "RSA" -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${sha256_dir}/client" -i "${sha256_dir}/int_ca" -a "RSA"
    create_crl "${sha256_dir}/int_ca"
    
    # SHA-512证书
    sha512_dir="$root_dir/sha512"
    mkdir -p "$sha512_dir"
    
    # 创建根CA
    create_root_ca -o "${sha512_dir}/root_ca" -a "RSA" -m "SHA512"
    
    # 创建中间CA（由根CA签名）
    create_intermediate_ca -o "${sha512_dir}/int_ca" -i "${sha512_dir}/root_ca" -a "RSA" -m "SHA512"
    
    # 创建服务器和客户端证书
    create_ee_cert -o "${sha512_dir}/server" -i "${sha512_dir}/int_ca" -a "RSA" -m "SHA512" -s "DNS:localhost,IP:127.0.0.1"
    create_ee_cert -o "${sha512_dir}/client" -i "${sha512_dir}/int_ca" -a "RSA" -m "SHA512"
    create_crl "${sha512_dir}/int_ca"
}

# 主函数
main() {
    log "开始生成测试证书..."
    
    # 检查OpenSSL版本
    check_openssl_version
    
    # 生成各种测试场景
    generate_algorithm_tests
    generate_chain_tests
    generate_revocation_tests
    generate_special_certs
    generate_hash_algorithm_tests
    
    log "证书生成完成！所有文件位于: $BASE_DIR"
}

# 执行主函数
main    
